package http

import (
	"bufio"
	"encoding/json"
	"fmt"
	"gitee.com/captials-team/ubdframe/src/common/consts"
	"gitee.com/captials-team/ubdframe/src/common/utils"
	"gitee.com/captials-team/ubdframe/src/domain/configstc"
	"gitee.com/captials-team/ubdframe/src/domain/dto/reqdata"
	"gitee.com/captials-team/ubdframe/src/domain/dto/respdata"
	"gitee.com/captials-team/ubdframe/src/domain/interfaces"
	"gitee.com/captials-team/ubdframe/src/pkg/gin_http"
	v1log "gitee.com/captials-team/ubdframe/src/pkg/logs"
	"gitee.com/captials-team/ubdframe/src/pkg/ossadaptor"
	"github.com/gin-gonic/gin"
	"go.uber.org/dig"
	"gorm.io/gorm"
	"io"
	"mime/multipart"
	"os"
	"path"
	"time"
)

type UploadController struct {
	l v1log.ILog

	conf *configstc.OssAppConfig

	settingDao interfaces.ItfSettingDao

	gin_http.ResponseController
}

func NewUploadController(di *dig.Container, l v1log.ILog, conf *configstc.OssAppConfig) *UploadController {
	ctr := &UploadController{
		l:    l,
		conf: conf,
	}

	di.Invoke(func(dao interfaces.ItfSettingDao) {
		ctr.settingDao = dao
	})

	return ctr
}

// ConfigOss 上传 godoc
// @Summary      OSS配置
// @Description  上传图片/文件等
// @Tags         upload
// @Produce      json
// @Param        param    body     reqdata.OssSettingReq  true  "参数"
// @success 200  {object} respdata.ResponseData{} "配置成功"
// @success 500 {object} respdata.ResponseData{} "配置失败"
// @Router       /oss/config [post]
func (ctr *UploadController) ConfigOss(ctx *gin.Context) {
	var req reqdata.OssSettingReq
	ctx.ShouldBindJSON(&req)

	upType := req.UploadType

	if _, exist := ossadaptor.SupportUploadTypes[upType]; !exist {
		ctr.Response(ctx, respdata.CError.MMsg("不支持的OSS配置类型"))
		return
	}
	upConfig := req.UploadConfig[upType]

	ctr.settingDao.DB().Transaction(func(tx *gorm.DB) error {
		dao := ctr.settingDao.Use(tx)
		err := dao.Set(UploadTypeKey, req.UploadType)
		if err != nil {
			return err
		}

		upConfigStr, _ := json.Marshal(upConfig)
		err = dao.Set(fmt.Sprintf(UploadConfigKey, upType), string(upConfigStr))
		if err != nil {
			return err
		}
		return nil
	})

	ctr.Response(ctx, respdata.CSuccess)
}

// DoUpload 上传 godoc
// @Summary      上传
// @Description  上传图片/文件等
// @Tags         upload
// @Produce      mpfd
// @Param        param    formData     file  true  "上传文件"
// @success 200  {object} respdata.ResponseData{} "上传成功"
// @success 500 {object} respdata.ResponseData{} "上传失败"
// @Router       /oss/upload [post]
func (ctr *UploadController) DoUpload(ctx *gin.Context) {
	upType, upConfig := ctr.getUploadConfig()
	ossHandler, err := ossadaptor.NewOssUploader(upType, upConfig)
	if err != nil {
		ctr.Response(ctx, respdata.CError.MMsg(err.Error()))
		return
	}

	_, files, err := ctr.ParseMultipartForm(ctx)
	if err != nil {
		ctr.l.Error("multipart form err: %s", err)
		ctr.Response(ctx, respdata.CError.MMsg("multipart form err: "+err.Error()))
		return
	}
	if files["file"] == nil && len(files["file"]) <= 0 {
		ctr.Response(ctx, respdata.CError.MMsg("请先上传文件"))
		return
	}

	file := files["file"][0]

	maxSize := ctr.conf.UploadMaxSize
	if file.Size > maxSize {
		ctr.Response(ctx, respdata.CError.MMsg(fmt.Sprintf("文件大小不可超过 %d b", maxSize)))
		return
	}
	//logger.Infof("fileSize:%d b", file.Size)

	//转存
	arFile, err := ctr.archivedFile(file)
	if err != nil {
		ctr.l.Error("upload fail: %s", err)
		ctr.Response(ctx, respdata.CError.MMsg("upload fail: "+err.Error()))
		return
	}

	//do upload
	tgName := ctr.objFileName(arFile)
	ret, err := ossHandler.Put(arFile, tgName)
	if err != nil {
		ctr.l.Error("upload fail: %s", err)
		ctr.Response(ctx, respdata.CError.MMsg("upload fail: "+err.Error()))
		return
	}

	ctr.Response(ctx, respdata.CSuccess.MData(map[string]interface{}{
		"url": ret.Url,
	}))
	return
}

func (ctr *UploadController) ParseMultipartForm(ctx *gin.Context) (map[string][]string, map[string][]*multipart.FileHeader, error) {
	r := ctx.Request
	err := r.ParseMultipartForm(128)
	if err != nil {
		return nil, nil, err
	}

	return r.MultipartForm.Value, r.MultipartForm.File, nil
}

func (ctr *UploadController) objFileName(file string) string {
	fSuffix := utils.FileExt(file)
	return fmt.Sprintf("%s.%s",
		utils.Md5Sum(time.Now().Format(time.DateTime)+utils.RandFigureCode(6)),
		fSuffix)
}

// 转存文件
func (ctr *UploadController) archivedFile(file *multipart.FileHeader) (string, error) {

	f, err := file.Open()
	defer func() {
		f.Close()
	}()
	if err != nil {
		return "", err
	}

	//临时存放目录
	tmpDir := path.Join(ctr.conf.StoragePath, "tmp")
	err = utils.EnsureDir(tmpDir)
	if err != nil {
		ctr.l.Error("create tmp dir err:%s", err)
		return "", err
	}

	//
	arName := fmt.Sprintf("%d_%s.%s",
		time.Now().UnixNano(), utils.RandFigureCode(4),
		utils.FileExt(file.Filename))
	arPath := path.Join(tmpDir, arName)

	fAr, err := os.OpenFile(arPath, os.O_CREATE|os.O_RDWR, 0755)
	if err != nil {
		return "", err
	}
	defer func() {
		fAr.Close()
	}()

	buf := bufio.NewReader(f)
	s := make([]byte, 4096)
	eof := false
	for !eof {
		n, err := buf.Read(s)
		if err == io.EOF {
			eof = true
		} else if err != nil {
			return "", err
		}

		if n <= 0 {
			continue
		}

		_, err = fAr.Write(s)
		if err != nil {
			return "", err
		}
	}

	return arPath, nil
}

// todo 优化
func (ctr *UploadController) readInputFile(f io.Reader, ch chan []byte) {
	defer close(ch)

	buf := bufio.NewReader(f)
	s := make([]byte, 4096)
	eof := false
	for !eof {
		n, err := buf.Read(s)
		if err == io.EOF {
			eof = true
		} else if err != nil {
			return
		}

		if n <= 0 {
			continue
		}

		ch <- s
	}
	return
}

// todo 优化
func (ctr *UploadController) writeOutFile(f io.Writer, ch chan []byte) {
	for s := range ch {
		_, err := f.Write(s)
		if err != nil {
			continue
		}
	}
	return
}

func (ctr *UploadController) getUploadConfig() (string, map[string]string) {
	c := ctr.conf
	switch c.ConfigSource {
	case consts.ConfigFromFile:
		return ctr.getUploadConfigFromFile()
	case consts.ConfigFromDB:
		return ctr.getUploadConfigFromDB()
	}

	return "", nil
}

// getUploadConfigFromFile 从配置文件取
func (ctr *UploadController) getUploadConfigFromFile() (string, map[string]string) {
	c := ctr.conf

	upType := c.UploadType
	upConfig := c.UploadConfig

	if upOss, exist := upConfig[upType]; exist {
		return upType, upOss
	}

	return upType, map[string]string{}
}

// getUploadConfigFromDB 从数据库取配置
func (ctr *UploadController) getUploadConfigFromDB() (string, map[string]string) {
	upConfig := map[string]string{}
	upType, err := ctr.settingDao.Get(UploadTypeKey)
	if err != nil {
		ctr.l.Error("get upload type from db err:%s", err)
		return upType, upConfig
	}

	if upType == "" {
		return upType, upConfig
	}
	//
	upConfigStr, err := ctr.settingDao.Get(fmt.Sprintf(UploadConfigKey, upType))
	if err != nil {
		ctr.l.Error("get upload config from db err:%s", err)
		return "", upConfig
	}

	json.Unmarshal([]byte(upConfigStr), &upConfig)

	return upType, upConfig
}

const UploadTypeKey = "oss_type"
const UploadConfigKey = "oss_config_%s"
